package ar.com.jsejtman.common.security.schema.entity;

import ar.com.jsejtman.common.domain.entity.Identifiable;
import ar.com.jsejtman.common.domain.entity.IdentifiableComparer;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;

/**
 * Usuario que puede loguearse en el sistema.
 * 
 * @author jsejtman
 */
@Entity
@Table(name = "security_security_user")
public class SecurityUser implements Identifiable<Long> {

	/**
	 * Identificador del usuario de seguridad.
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id")
	private Long id;

	/**
	 * Nombre de usuario (no es el nombre y apellido, sino el usuario con el cual puede ingresar al
	 * sistema).
	 */
	@Column(name = "username", nullable = false)
	private String username;

	/**
	 * Clave para ingreso al sistema.
	 */
	@Column(name = "password", nullable = false)
	private String password;

	/**
	 * Primer (y segundo) nombre del usuario.
	 */
	@Column(name = "first_name", nullable = false)
	private String firstName;

	/**
	 * Apellido del usuario.
	 */
	@Column(name = "last_name", nullable = false)
	private String lastName;

	/**
	 * Dirección de correo electrónico del usuario (útil para recuperar una clave perdida, por
	 * ejemplo).
	 */
	@Column(name = "email_address", nullable = false)
	private String emailAddress;

	/**
	 * Fecha de deshabilitación del usuario ("Null" si el usuario está habilitado).
	 */
	@Column(name = "disabled_date", nullable = true)
	private Date disabledDate;

	/**
	 * Fecha a partir de la cual el usuario debe renovar su clave de acceso ("Null" si debe
	 * renovarla en el próximo acceso, sin importar la fecha).
	 */
	@Column(name = "credentials_expiration_date", nullable = true)
	private Date credentialsExpirationDate;

	/**
	 * Lista de sobreescrituras de autoridades (para asignar o restringir más permisos).
	 */
	@OneToMany(mappedBy = "securityUser", fetch = FetchType.LAZY, cascade = CascadeType.ALL,
	           orphanRemoval = true)
	private List<AuthorityOverride> authoritiesOverrides;

	/**
	 * Grupos de autoridades asignados al usuario (es decir, grupos de permisos sobre acciones que
	 * el usuario puede realizar en la aplicación).
	 */
	@ManyToMany
	@JoinTable(name = "security_user_authorities_group", 
			joinColumns = { @JoinColumn(name = "security_user_id") }, 
			inverseJoinColumns = { @JoinColumn(name = "authorities_group_id") })
	private List<AuthoritiesGroup> authoritiesGroups;

	/**
	 * Enlaces del usuario de seguridad a elementos del dominio de la aplicación.
	 */
	@OneToMany(mappedBy = "securityUser", fetch = FetchType.LAZY, cascade = CascadeType.ALL,
	           orphanRemoval = true)
	private List<SecurityToDomainUserLink> domainUserLinks;

	/**
	 * Número de versión de la entidad (para evitar escrituras concurrentes).
	 */
	@Version
	@Column(name = "version", nullable = false)
	private Long version;

	/**
	 * Devuelve el valor de id.
	 * 
	 * @return El valor de id.
	 */
	public final Long getId() {

		return (this.id);
	}

	/**
	 * Asigna un nuevo valor a id.
	 * 
	 * @param id El valor a asignar a id.
	 */
	protected final void setId(final Long id) {

		this.id = id;
	}

	/**
	 * Devuelve el valor de username.
	 * 
	 * @return El valor de username.
	 */
	public final String getUsername() {

		return (this.username);
	}

	/**
	 * Asigna un nuevo valor a username.
	 * 
	 * @param username El valor a asignar a username.
	 */
	public final void setUsername(final String username) {

		this.username = username;
	}

	/**
	 * Devuelve el valor de password.
	 * 
	 * @return El valor de password.
	 */
	public final String getPassword() {

		return (this.password);
	}

	/**
	 * Asigna un nuevo valor a password.
	 * 
	 * @param password El valor a asignar a password.
	 */
	public final void setPassword(final String password) {

		this.password = password;
	}

	/**
	 * Devuelve el valor de firstName.
	 * 
	 * @return El valor de firstName.
	 */
	public final String getFirstName() {

		return (this.firstName);
	}

	/**
	 * Asigna un nuevo valor a firstName.
	 * 
	 * @param firstName El valor a asignar a firstName.
	 */
	public final void setFirstName(final String firstName) {

		this.firstName = firstName;
	}

	/**
	 * Devuelve el valor de lastName.
	 * 
	 * @return El valor de lastName.
	 */
	public final String getLastName() {

		return (this.lastName);
	}

	/**
	 * Asigna un nuevo valor a lastName.
	 * 
	 * @param lastName El valor a asignar a lastName.
	 */
	public final void setLastName(final String lastName) {

		this.lastName = lastName;
	}

	/**
	 * Devuelve el valor de emailAddress.
	 * 
	 * @return El valor de emailAddress.
	 */
	public final String getEmailAddress() {

		return (this.emailAddress);
	}

	/**
	 * Asigna un nuevo valor a emailAddress.
	 * 
	 * @param emailAddress El valor a asignar a emailAddress.
	 */
	public final void setEmailAddress(final String emailAddress) {

		this.emailAddress = emailAddress;
	}

	/**
	 * Devuelve el valor de disabledDate.
	 * 
	 * @return El valor de disabledDate.
	 */
	public final Date getDisabledDate() {

		return (this.disabledDate);
	}

	/**
	 * Asigna un nuevo valor a disabledDate.
	 * 
	 * @param disabledDate El valor a asignar a disabledDate.
	 */
	public final void setDisabledDate(final Date disabledDate) {

		this.disabledDate = disabledDate;
	}

	/**
	 * Devuelve el valor de credentialsExpirationDate.
	 * 
	 * @return El valor de credentialsExpirationDate.
	 */
	public final Date getCredentialsExpirationDate() {

		return (this.credentialsExpirationDate);
	}

	/**
	 * Asigna un nuevo valor a credentialsExpirationDate.
	 * 
	 * @param credentialsExpirationDate El valor a asignar a credentialsExpirationDate.
	 */
	public final void setCredentialsExpirationDate(final Date credentialsExpirationDate) {

		this.credentialsExpirationDate = credentialsExpirationDate;
	}

	/**
	 * Devuelve el valor de authoritiesOverride.
	 * 
	 * @return El valor de authoritiesOverride.
	 */
	protected final List<AuthorityOverride> getAuthoritiesOverride() {

		return (this.authoritiesOverrides);
	}

	/**
	 * Asigna un nuevo valor a authoritiesOverride.
	 * 
	 * @param authoritiesOverride El valor a asignar a authoritiesOverride.
	 */
	protected final void setAuthoritiesOverride(final List<AuthorityOverride> authoritiesOverride) {

		this.authoritiesOverrides = authoritiesOverride;
	}

	/**
	 * Agrega una nueva sobreescritura de autoridad.
	 * 
	 * @param authorityOverride Sobreescritura de autoridad a agregar.
	 * @return True si la agregó; caso contrario, false.
	 */
	public final boolean addAuthorityOverride(final AuthorityOverride authorityOverride) {

		if (authorityOverride == null) {
			return (false);
		}

		/* Instancia la colección */
		if (this.authoritiesOverrides == null) {
			this.authoritiesOverrides = new ArrayList<AuthorityOverride>();
		}

		final boolean couldAddEntity = this.authoritiesOverrides.add(authorityOverride);

		if (couldAddEntity) {
			authorityOverride.setSecurityUser(this);
		}

		return (couldAddEntity);
	}

	/**
	 * Quita una sobreescritura de autoridad.
	 * 
	 * @param authorityOverride Sobreescritura de autoridad a quitar.
	 * @return True si la quitó; caso contrario, false.
	 */
	public final boolean removeAuthorityOverride(final AuthorityOverride authorityOverride) {

		/*
		 * No puede intentar remover objetos nulos, o cuando la colección aún no ha sido
		 * inicializada.
		 */
		if (authorityOverride == null || this.authoritiesOverrides == null) {
			return (false);
		}

		return (this.authoritiesOverrides.remove(authorityOverride));
	}

	/**
	 * Obtiene la lista de todas las sobreescrituras de autoridades.
	 * 
	 * @return Lista de sobreescrituras de autoridades.
	 */
	public final List<AuthorityOverride> getAllAuthoritiesOverrides() {

		/*
		 * Si la colección no fue inicializada, devuelve una colección vacía (impide la generación
		 * de punteros nulos).
		 */
		if (this.authoritiesOverrides == null) {
			return (Collections.unmodifiableList(new ArrayList<AuthorityOverride>()));
		}

		return (Collections.unmodifiableList(this.authoritiesOverrides));
	}

	/**
	 * Devuelve el valor de authoritiesGroups.
	 * 
	 * @return El valor de authoritiesGroups.
	 */
	protected final List<AuthoritiesGroup> getAuthoritiesGroups() {

		return (this.authoritiesGroups);
	}

	/**
	 * Asigna un nuevo valor a authoritiesGroups.
	 * 
	 * @param authoritiesGroups El valor a asignar a authoritiesGroups.
	 */
	protected final void setAuthoritiesGroups(final List<AuthoritiesGroup> authoritiesGroups) {

		this.authoritiesGroups = authoritiesGroups;
	}

	/**
	 * Agrega una nueva autoridad al grupo de autoridades.
	 * 
	 * @param authorityGroup Autoridad a agregar.
	 * @return True si la agregó; caso contrario, false.
	 */
	public final boolean addAuthorityGroup(final AuthoritiesGroup authorityGroup) {

		/* Instancia la colección */
		if (this.authoritiesGroups == null) {
			this.authoritiesGroups = new ArrayList<AuthoritiesGroup>();
		}

		return (this.authoritiesGroups.add(authorityGroup));
	}

	/**
	 * Quita un grupo de autoridades de la lista de grupos.
	 * 
	 * @param authorityGroup Grupo a quitar.
	 * @return True si lo quitó; caso contrario, false.
	 */
	public final boolean removeAuthorityGroup(final AuthoritiesGroup authorityGroup) {

		/*
		 * No puede intentar remover objetos nulos, o cuando la colección aún no ha sido
		 * inicializada.
		 */
		if (authorityGroup == null || this.authoritiesGroups == null) {
			return (false);
		}

		return (this.authoritiesGroups.remove(authorityGroup));
	}

	/**
	 * Obtiene la lista de todos los grupos de autoridades.
	 * 
	 * @return Lista de grupos de autoridades.
	 */
	public final List<AuthoritiesGroup> getAllAuthorityGroups() {

		/*
		 * Si la colección no fue inicializada, devuelve una colección vacía (impide la generación
		 * de punteros nulos).
		 */
		if (this.authoritiesGroups == null) {
			return (Collections.unmodifiableList(new ArrayList<AuthoritiesGroup>()));
		}

		return (Collections.unmodifiableList(this.authoritiesGroups));
	}

	/**
	 * Devuelve el valor de domainUserLinks.
	 * 
	 * @return El valor de domainUserLinks.
	 */
	protected final List<SecurityToDomainUserLink> getDomainUserLinks() {

		return (this.domainUserLinks);
	}

	/**
	 * Asigna un nuevo valor a domainUserLinks.
	 * 
	 * @param domainUserLinks El valor a asignar a domainUserLinks.
	 */
	protected final void setDomainUserLinks(final List<SecurityToDomainUserLink> domainUserLinks) {

		this.domainUserLinks = domainUserLinks;
	}

	/**
	 * Agrega un enlace a usuario de dominio al usuario de seguridad.
	 * 
	 * @param link Enlace a usuario de dominio a agregar.
	 * @return True si lo agregó; caso contrario, false.
	 */
	public final boolean addDomainUserLink(final SecurityToDomainUserLink link) {

		if (link == null) {
			return (false);
		}

		/* Instancia la colección */
		if (this.domainUserLinks == null) {
			this.domainUserLinks = new ArrayList<SecurityToDomainUserLink>();
		}

		final boolean couldAddEntity = this.domainUserLinks.add(link);

		if (couldAddEntity) {
			link.setSecurityUser(this);
		}

		return (couldAddEntity);
	}

	/**
	 * Quita un enlace a usuario de dominio del usuario de seguridad.
	 * 
	 * @param link Enlace a usuario de dominio a quitar.
	 * @return True si lo quitó; caso contrario, false.
	 */
	public final boolean removeDomainUserLink(final SecurityToDomainUserLink link) {

		/*
		 * No puede intentar remover objetos nulos, o cuando la colección aún no ha sido
		 * inicializada.
		 */
		if (link == null || this.domainUserLinks == null) {
			return (false);
		}

		return (this.domainUserLinks.remove(link));
	}

	/**
	 * Obtiene la lista de todos los enlaces a usuarios de dominio.
	 * 
	 * @return Lista de enlaces a usuarios de dominio.
	 */
	public final List<SecurityToDomainUserLink> getAllDomainUserLinks() {

		/*
		 * Si la colección no fue inicializada, devuelve una colección vacía (impide la generación
		 * de punteros nulos)
		 */
		if (this.domainUserLinks == null) {
			return (Collections.unmodifiableList(new ArrayList<SecurityToDomainUserLink>()));
		}

		return (Collections.unmodifiableList(this.domainUserLinks));
	}

	/**
	 * Devuelve el valor de version.
	 * 
	 * @return El valor de version.
	 */
	protected final Long getVersion() {

		return (this.version);
	}

	/**
	 * Asigna un nuevo valor a version.
	 * 
	 * @param version El valor a asignar a version.
	 */
	protected final void setVersion(final Long version) {

		this.version = version;
	}

	@Override
	public final int hashCode() {

		return ((this.id == null) ? 0 : this.id.hashCode());
	}

	@SuppressWarnings("unchecked")
	@Override
	public final boolean equals(final Object obj) {

		return (IdentifiableComparer.equals(this, (Identifiable<Serializable>) obj));
	}
}
